// This program implements a vector addition using OpenCL

// System includes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "adcUtilsOpenCL.h"

// OpenCL includes
#include <CL/cl.h>

int main(int argc, char* argv[]){
    // This code executes on the OpenCL host

    bool debug = false;
    /*Tamaño de la matriz*/
    int N = atoi(argv[2]);

    /*Cantidad de iteraciones*/
    int k = atoi(argv[3]);
    // Host data
    int iterations=1;/*iterations inside kernel*/
    int loops=1; /*iterations in host*/
    /*Modo express o real-time*/
    bool realTimeModeOn;
    if (strcmp(argv[1], "r") == 0) {
        realTimeModeOn = true;
        loops = k;
    } else if (strcmp(argv[1], "e") == 0) {
        realTimeModeOn = false;
        iterations = k;
    }else{
        printf("Mode no selected\n");
    }


    /*Cantidad de fuentes de calor*/
    /*En el ejercicio se sujeria usar como nombre de variable la letra "j", se ovio esto porque es mas comodo usar
    esa variable en loops. Se renombro como "tempCounter" (contador de temperaturas)*/
    int tempCounter = atoi(argv[4]);

    int i, j;
    int offset;

    /*Usado para input*/
    float foil[N][N];
    /*Usado para output o input/output*/
    float result[N][N];
    for (i = 0; i < N; ++i) {
        for (j = 0; j < N; ++j) {
            result[i][j] = -1.0;
        }
    }
    /*Inicializo la matriz de entrada. Se supone temperatura incial 0ºC*/
    for (i = 0; i < N; ++i) {
        for (j = 0; j < N; ++j) {
            foil[i][j] = 0.0;
        }
    }

    int X[tempCounter];
    int Y[tempCounter];
    float T[tempCounter];

    for (i = 0; i < tempCounter; i++) {
        offset=i*3+5;/* *3 porque come de a 3 elementos, +5 porquelos primeros 5 parametros no sirven para esto*/
        X[i]=atoi(argv[offset]);
        Y[i]=atoi(argv[offset+1]);
        T[i]=atof(argv[offset+2]);
    }

    /*Datasize*/
    size_t foilDatasize = N*N*sizeof(float);
    size_t xDatasize = tempCounter*sizeof(int);
    size_t yDatasize = tempCounter*sizeof(int);
    size_t tDatasize = tempCounter*sizeof(float);

    //———————————————————————————————————————————————————
    // STEP 1: Set Up Environment
    //———————————————————————————————————————————————————

    // Use this to check the output of each API call
    cl_int ciErrNum;

    cl_platform_id platform;

    // Use the first platforms
    ciErrNum = clGetPlatformIDs(1, &platform, NULL);
    if(debug){
        if (ciErrNum == CL_SUCCESS) {
            printf("First platform chosen...\n");
        } else {
            printf("Error platform chosen\n");
        }
    }
    cl_device_id device;

    // Use the first device
    ciErrNum = clGetDeviceIDs(platform, CL_DEVICE_TYPE_ALL, 1, &device, NULL);
    if(debug) {
        if (ciErrNum == CL_SUCCESS) {
            printf("First device chosen...\n");
        } else {
            printf("Error device chosen\n");
        }
    }
    cl_context_properties cps[3] = {
            CL_CONTEXT_PLATFORM, (cl_context_properties)platform, 0 };

    // Create the context
    cl_context ctx;
    ctx = clCreateContext(cps, 1, &device, NULL, NULL, &ciErrNum);
    if(debug){
        if (ciErrNum == CL_SUCCESS) {
            printf("Context created\n");
        } else {
            printf("Error creating context\n");
        }
    }
    // Create the command queue
    cl_command_queue myqueue;
    myqueue = clCreateCommandQueue(ctx, device, CL_QUEUE_PROFILING_ENABLE, &ciErrNum);
    if(debug) {
        if (ciErrNum == CL_SUCCESS) {
            printf("Command queue created\n");
        } else {
            printf("Error creating command queue\n");
        }
    }
    //———————————————————————————————————————————————————
    // STEP 2: Declare Buffers and Move Data
    //———————————————————————————————————————————————————

    // Create a buffer object that will contain the data
    // from the host
    cl_mem bufFoil;
    cl_mem bufX;
    cl_mem bufY;
    cl_mem bufT;

    bufFoil = clCreateBuffer(ctx, CL_MEM_READ_WRITE, foilDatasize, NULL, &ciErrNum);
    if(debug) {
        if (ciErrNum == CL_SUCCESS) {
            printf("Buffer bufFoil created\n");
        } else {
            printf("Error creating bufFoil\n");
        }
    }
    bufX = clCreateBuffer(ctx, CL_MEM_READ_ONLY, xDatasize, NULL, &ciErrNum);
    if(debug) {
        if (ciErrNum == CL_SUCCESS) {
            printf("Buffer bufX created\n");
        } else {
            printf("Error creating bufX\n");
        }
    }
    bufY = clCreateBuffer(ctx, CL_MEM_READ_ONLY, yDatasize, NULL, &ciErrNum);
    if(debug) {
        if (ciErrNum == CL_SUCCESS) {
            printf("Buffer bufY created\n");
        } else {
            printf("Error creating bufY\n");
        }
    }
    bufT = clCreateBuffer(ctx, CL_MEM_READ_ONLY, tDatasize, NULL, &ciErrNum);
    if(debug) {
        if (ciErrNum == CL_SUCCESS) {
            printf("Buffer bufFoil created\n");
        } else {
            printf("Error creating bufFoil\n");
        }
    }
    // Write input to the device buffer
    ciErrNum = clEnqueueWriteBuffer(myqueue, bufFoil, CL_TRUE, 0, foilDatasize,
            (void *)foil, 0, NULL, NULL);
    if(debug) {
        if (ciErrNum == CL_SUCCESS) {
            printf("Command EnqueueWriteBuffer(bufFoil): OK\n");
        } else {
            printf("ERROR EnqueueWriteBuffer(bufFoil)\n");
        }
    }
    ciErrNum = clEnqueueWriteBuffer(myqueue, bufX, CL_TRUE, 0, xDatasize,
            (void *)X, 0, NULL, NULL);
    if(debug) {
        if (ciErrNum == CL_SUCCESS) {
            printf("Command EnqueueWriteBuffer(bufX): OK\n");
        } else {
            printf("ERROR EnqueueWriteBuffer(bufX)\n");
        }
    }
    ciErrNum = clEnqueueWriteBuffer(myqueue, bufY, CL_TRUE, 0, yDatasize,
            (void *)Y, 0, NULL, NULL);
    if(debug) {
        if (ciErrNum == CL_SUCCESS) {
            printf("Command EnqueueWriteBuffer(bufY): OK\n");
        } else {
            printf("ERROR EnqueueWriteBuffer(bufY)\n");
        }
    }
    ciErrNum = clEnqueueWriteBuffer(myqueue, bufT, CL_TRUE, 0, tDatasize,
            (void *)T, 0, NULL, NULL);
    if(debug) {
        if (ciErrNum == CL_SUCCESS) {
            printf("Command EnqueueWriteBuffer(bufT): OK\n");
        } else {
            printf("ERROR EnqueueWriteBuffer(bufT)\n");
        }
    }
    //———————————————————————————————————————————————————
    // STEP 3: Runtime Kernel Compilation
    //———————————————————————————————————————————————————

    char* kernelPath = (char*)malloc((strlen("kernel.cl")+1)*sizeof(char));

    strcpy(kernelPath,"kernel.cl");

    char* programSource = readSource(kernelPath);

    // Create a program with source code
    cl_program myprog = clCreateProgramWithSource(ctx, 1, (const char**)&programSource, NULL, &ciErrNum);

    if(debug) {
        if (ciErrNum == CL_SUCCESS) {
            printf("Program with source created\n");
        } else {
            printf("Error creating program with source\n");
        }
    }
    // Build (compile) the program for the device
    ciErrNum = clBuildProgram(myprog, 1, &device, NULL, NULL, NULL);

    if(debug) {
        if (ciErrNum == CL_SUCCESS) {
            printf("Program built\n");
        } else {
            printf("Error biulding program. Number error: %u\n", ciErrNum);
        }
    }
    // Create the vector addition kernel
    cl_kernel mykernel;
    mykernel = clCreateKernel(myprog, "openCLkernel", &ciErrNum);

    if(debug) {
        if (ciErrNum == CL_SUCCESS) {
            printf("Kernel created\n");
        } else {
            printf("Error creating Kernel\n");
        }
    }

    //———————————————————————————————————————————————————
    // STEP 4: Run the program and Return result to host
    //———————————————————————————————————————————————————
    cl_int status;
    // Associate the input and output buffers with the kernel
    status = clSetKernelArg(mykernel, 0, sizeof(cl_mem), &bufFoil);
    if(debug) {
        if (status == CL_SUCCESS) {
            printf("Arg 0 OK!\n");
        } else {
            printf("Error passing Arg 0\n");
        }
    }
    status = clSetKernelArg(mykernel, 1, sizeof(cl_int), &tempCounter);
    if(debug) {
        if (status == CL_SUCCESS) {
            printf("Arg 1 OK!\n");
        } else {
            printf("Error passing Arg 1\n");
        }
    }
    status = clSetKernelArg(mykernel, 2, sizeof(cl_mem), &bufX);
    if(debug) {
        if (status == CL_SUCCESS) {
            printf("Arg 2 OK!\n");
        } else {
            printf("Error passing Arg 2\n");
        }
    }
    status = clSetKernelArg(mykernel, 3, sizeof(cl_mem), &bufY);
    if(debug) {
        if (status == CL_SUCCESS) {
            printf("Arg 3 OK!\n");
        } else {
            printf("Error passing Arg 3\n");
        }
    }
    status = clSetKernelArg(mykernel, 4, sizeof(cl_mem), &bufT);
    if(debug) {
        if (status == CL_SUCCESS) {
            printf("Arg 4 OK!\n");
        } else {
            printf("Error passing Arg 4\n");
        }
    }
    status = clSetKernelArg(mykernel, 5, sizeof(cl_int), &N);
    if(debug) {
        if (status == CL_SUCCESS) {
            printf("Arg 5 OK!\n");
        } else {
            printf("Error passing Arg 5\n");
        }
    }
    status = clSetKernelArg(mykernel, 6, sizeof(cl_int), &iterations);
    if(debug) {
        if (status == CL_SUCCESS) {
            printf("Arg 6 OK!\n");
        } else {
            printf("Error passing Arg 6\n");
        }
    }
    // Set local and global workgroup sizes
    size_t localWS[2] = {N,N};
    size_t globalWS[2] = {N,N};

    // Execute kernel
    cl_event event;
    cl_ulong time_start, time_end;
    double delta_time = 0;
    int p;
    for (p = 0; p < loops; p++) {
        clFinish(myqueue);

        ciErrNum = clEnqueueNDRangeKernel(myqueue, mykernel, 2, NULL, globalWS,
                localWS, 0, NULL, &event);

        if(debug) {
            if (ciErrNum == CL_SUCCESS) {
                printf("Enqueue Range Kernel created\n");
            } else {
                printf("Error creating Enqueue Range Kernel\n");
            }
        }

        clWaitForEvents(1, &event);

        cl_int RSError;

        // Read the device output buffer to the host output array
        RSError = clEnqueueReadBuffer(myqueue, bufFoil, CL_TRUE, 0, foilDatasize,
                (void *)foil, 0, NULL, NULL);

        if(debug) {
            if (RSError == CL_SUCCESS) {
                printf("ReadBuffer OK!\n");
            } else {
                printf("Read buffer error: %i\n", RSError);
            }
        }

        clGetEventProfilingInfo(event, CL_PROFILING_COMMAND_START, sizeof(cl_ulong), &time_start, NULL);
        clGetEventProfilingInfo(event, CL_PROFILING_COMMAND_END, sizeof(cl_ulong), &time_end, NULL);
        delta_time += time_end - time_start;

        /*OpenCL Matriz*/
        printf("Matriz\n");
        for(i = 0; i < N; i++) {
            for(j = 0; j < N; j++){
                printf("%.2f ",foil[i][j]);
            }
            printf("\n");
        }

    }

    printf("Execution time in miliseconds = %.3fms\n", (delta_time/1000000.0));

    //———————————————————————————————————————————————————
    // STEP 5: Release OpenCL resources
    //———————————————————————————————————————————————————

    // Free OpenCL resources
    clReleaseKernel(mykernel);
    clReleaseProgram(myprog);
    clReleaseCommandQueue(myqueue);
    clReleaseMemObject(bufFoil);
    clReleaseContext(ctx);

    return 0;
}
